use std::collections::HashMap;
use std::fmt::{Display, Formatter};

pub mod lexer;
pub mod parser;
pub mod error;


#[derive(Debug, Clone, PartialEq)]
pub enum Number {
    Int(i32),
    Float(f64),
}

impl Display for Number {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Number::Int(value) => {
                writeln!(f, "{}", *value)
            }
            Number::Float(value) => {
                writeln!(f, "{}", *value)
            }
        }
    }
}
#[derive(Debug, Clone,PartialEq)]
pub enum Json
{
    String(String),
    Number(Number),
    Object(HashMap<String, Json>),
    Array(Vec<Json>),
    Boolean(bool),
    Null,
}

impl Json {
    pub fn new() -> Json {
        Json::Null
    }
    pub fn from_string(str: &str) -> Json {
        Json::String(str.to_string())
    }

    pub fn from_integer(value: i32) -> Json {
        Json::Number(Number::Int(value))
    }

    pub fn from_float(value: f64) -> Json {
        Json::Number(Number::Float(value))
    }

    pub fn from_bool(value: bool) -> Json {
        Json::Boolean(value)
    }

    pub fn from_object(value: HashMap<String, Json>) -> Json {
        Json::Object(value)
    }

    pub fn from_array(value: Vec<Json>) -> Json {
        Json::Array(value)
    }

    pub fn to_string(&self) -> String {
        match self {
            Json::String(str) => {
                format!("\"{}\"", *str)
            }
            Json::Number(n) => {
                match n {
                    Number::Int(temp) => temp.to_string(),
                    Number::Float(temp) => temp.to_string(),
                }
            }
            Json::Object(obj) => {
                Self::json_obj_to_string(obj)
            }
            Json::Array(arr) => {
                Self::json_arr_to_string(arr)
            }
            Json::Boolean(b) => {
                if *b {
                    "true".to_string()
                } else {
                    "false".to_string()
                }
            }
            Json::Null => "null".to_string(),
        }
    }

    fn json_obj_to_string(obj: &HashMap<String, Json>) -> String {
        let mut res_str = String::new();
        let mut counter = 0;
        res_str.push('{');
        for (key, value) in obj {
            if counter == 0 {
                res_str.push_str(&format!("\"{}\" : {}", *key, value.to_string()));
            } else {
                let temp_str = format!(", \"{}\" : {}", *key, value.to_string());
                res_str.push_str(&temp_str);
            }
            counter += 1;
        }
        res_str.push('}');
        return res_str;
    }

    fn json_arr_to_string(arr: &Vec<Json>) -> String {
        let mut res_str = String::new();
        res_str.push('[');
        for (index, value) in arr.iter().enumerate() {
            if index == 0 {
                res_str.push_str(&value.to_string());
            } else {
                res_str.push_str(&format!(", {}", &value.to_string()));
            }
        }
        res_str.push(']');
        return res_str;
    }
}

impl Display for Json {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{}", self.to_string())
    }
}

#[cfg(test)]
mod json_test {
    use std::collections::HashMap;

    use crate::json::Json;

    #[test]
    fn test_json_create() {
        let _ = Json::new();
        let _ = Json::from_array(vec![]);
        let _ = Json::from_bool(true);
        let _ = Json::from_float(0.0);
        let _ = Json::from_integer(10);
        let _ = Json::from_object(HashMap::new());
        let _ = Json::from_string("hello");
    }

    #[test]
    fn test_json_to_string() {
        let mut v1 = HashMap::<String, Json>::new();
        v1.insert("key1".to_string(), Json::from_string("hello"));
        let vcc = vec![Json::from_bool(true), Json::from_string("hello"), Json::new()];
        v1.insert("key2".to_string(), Json::from_array(vcc));
        let v3 = v1.clone();
        v1.insert("key3".to_string(), Json::from_object(v3));
        let cc2 = Json::from_object(v1);
        println!("{}", cc2)
    }
}